package com.shm.leetcode;

/**
 * 1423. 可获得的最大点数
 * 几张卡牌 排成一行，每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。
 *
 * 每次行动，你可以从行的开头或者末尾拿一张卡牌，最终你必须正好拿 k 张卡牌。
 *
 * 你的点数就是你拿到手中的所有卡牌的点数之和。
 *
 * 给你一个整数数组 cardPoints 和整数 k，请你返回可以获得的最大点数。
 *
 *
 *
 * 示例 1：
 *
 * 输入：cardPoints = [1,2,3,4,5,6,1], k = 3
 * 输出：12
 * 解释：第一次行动，不管拿哪张牌，你的点数总是 1 。但是，先拿最右边的卡牌将会最大化你的可获得点数。最优策略是拿右边的三张牌，最终点数为 1 + 6 + 5 = 12 。
 * 示例 2：
 *
 * 输入：cardPoints = [2,2,2], k = 2
 * 输出：4
 * 解释：无论你拿起哪两张卡牌，可获得的点数总是 4 。
 * 示例 3：
 *
 * 输入：cardPoints = [9,7,7,9,7,7,9], k = 7
 * 输出：55
 * 解释：你必须拿起所有卡牌，可以获得的点数为所有卡牌的点数之和。
 * 示例 4：
 *
 * 输入：cardPoints = [1,1000,1], k = 1
 * 输出：1
 * 解释：你无法拿到中间那张卡牌，所以可以获得的最大点数为 1 。
 * 示例 5：
 *
 * 输入：cardPoints = [1,79,80,1,1,1,200,1], k = 3
 * 输出：202
 *
 *
 * 提示：
 *
 * 1 <= cardPoints.length <= 10^5
 * 1 <= cardPoints[i] <= 10^4
 * 1 <= k <= cardPoints.length
 * @author SHM
 */
public class MaxScore {
    /**
     * 方法一：滑动窗口
     * 思路
     *
     * 记数组 \textit{cardPoints}cardPoints 的长度为 nn，由于只能从开头和末尾拿 kk 张卡牌，所以最后剩下的必然是连续的 n-kn−k 张卡牌。
     *
     * 我们可以通过求出剩余卡牌点数之和的最小值，来求出拿走卡牌点数之和的最大值。
     *
     * 算法
     *
     * 由于剩余卡牌是连续的，使用一个固定长度为 n-kn−k 的滑动窗口对数组 \textit{cardPoints}cardPoints 进行遍历，求出滑动窗口最小值，然后用所有卡牌的点数之和减去该最小值，即得到了拿走卡牌点数之和的最大值。
     *
     * 复杂度分析
     *
     * 时间复杂度：O(n)O(n)，其中 nn 是数组 \textit{cardPoints}cardPoints 的长度。
     *
     * 空间复杂度：O(1)O(1)。
     *
     * 作者：LeetCode-Solution
     * 链接：https://leetcode-cn.com/problems/maximum-points-you-can-obtain-from-cards/solution/ke-huo-de-de-zui-da-dian-shu-by-leetcode-7je9/
     * @param cardPoints
     * @param k
     * @return
     */
    public int maxScore(int[] cardPoints, int k) {
        int n = cardPoints.length;
        int sum = 0;
        for (int i = 0; i < n-k; i++) {
            sum+=cardPoints[i];
        }
        int min = sum;
        for (int i = n-k; i < n; i++) {
            sum+=cardPoints[i];
            sum-=cardPoints[i+k-n];
            min = Math.min(min,sum);
        }

        int ss = 0;
        for (int cardPoint : cardPoints) {
            ss+=cardPoint;
        }
        return ss-min;
    }

//    本质是，求数组开始的一段和数组结尾的一段，这两段之和。
//    保持两段的总长度为k，维护首尾两个滑动窗口即可。
    // 方法1：动态维护窗口中的数，双指针思想
    public int maxScore_2(int[] cardPoints, int k) {
        int sum = 0;
        for (int i = 0; i < k; i++) {
            sum += cardPoints[i];
        }
        // ans初始化为前k个数的和
        int ans = sum;
        int n = cardPoints.length;
        for (int i = k - 1; i >= 0; i--) {
            // 每次加入后半部分的一个数，并删掉前半部分的最后一个数
            sum += cardPoints[n + i - k] - cardPoints[i];
            // 更新答案
            ans = Math.max(ans, sum);
        }
        return ans;
    }

//    作者：liberg
//    链接：https://leetcode-cn.com/problems/maximum-points-you-can-obtain-from-cards/solution/javaliang-chong-jie-fa-gan-jing-li-luo-b-pjd3/
}
